/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "pythoninterpreterlineedit.h"

#include <QKeyEvent>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QDebug>
#include <QCompleter>
#include <QStringListModel>
#include <QAbstractItemView>

namespace py = pybind11;

PythonInterpreterLineEdit::PythonInterpreterLineEdit(QWidget *parent):
    QLineEdit (parent)
{
    connect(this, &PythonInterpreterLineEdit::textEdited, this, &PythonInterpreterLineEdit::updateCompleter);
    QCompleter * completer = new QCompleter(this);
    completer->setModel(new QStringListModel(completer));
    setCompleter(completer);
    try
    {
        _rlcompleter = py::module::import("rlcompleter");
    }
    catch (const std::exception& exc)
    {
        qWarning("%s", exc.what());
    }
}

void PythonInterpreterLineEdit::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
    {
        if(_history.empty())
        {
            // do nothing
        }
        else
        {
            if(_history_index == 0)
            {
                // cannot go up
            }
            else
            {
                --_history_index;
                setText(_history[_history_index]);
            }
        }
    }
    else if (event->key() == Qt::Key_Down)
    {
        if(_history.empty())
        {
            // do nothing
        }
        else
        {
            if(_history_index >= _history.size()-1)
            {
                // cannot go down
            }
            else
            {
                ++_history_index;
                setText(_history[_history_index]);
            }
        }
    }
    else if (event->key() == Qt::Key_Return && completer() && completer()->popup()->isVisible())
    {
        event->ignore();
    }
    else
    {
        QLineEdit::keyPressEvent(event);
    }
}

bool PythonInterpreterLineEdit::event(QEvent *event)
{
    if(event->type() == QEvent::KeyPress)
    {
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
        if(keyEvent->key() == Qt::Key_Tab)
        {
            setText(text().insert(cursorPosition(), "    "));
            event->accept();
            return true;
        }
//        if(keyEvent->key() == Qt::Key_Space && (keyEvent->modifiers() & Qt::ControlModifier))
//        {
//            qDebug() << "completion";
////            const std::string& input = text().toStdString();

////            qDebug() << tips;
//            return true;
//        }
    }
    return QLineEdit::event(event);
}

QStringList PythonInterpreterLineEdit::getCompleterList(const QString &text)
{
    QStringList tips;
    const std::string& input = text.toStdString();
    try
    {
        py::object completer = _rlcompleter.attr("Completer")(_globals);
        int i = 0;
        py::object result = completer.attr("complete")(input, i);
        while(!result.is_none())
        {
            tips.append(QString::fromStdString(result.cast<std::string>()));
            ++i;
            result = completer.attr("complete")(input, i);
        }
    }
    catch (const std::exception& exc)
    {
        qWarning("%s", exc.what());
    }
    return tips;
}

void PythonInterpreterLineEdit::finished(int)
{
    _history.push_back(text());
}

void PythonInterpreterLineEdit::updateCompleter(const QString &text)
{
    QStringListModel* model = static_cast<QStringListModel*>(completer()->model());
    model->setStringList(getCompleterList(text));
}

PythonInterpreterWidget::PythonInterpreterWidget(QWidget *parent):
    QWidget (parent),
    line_edit(new PythonInterpreterLineEdit(this))
{
    QHBoxLayout * hbox = new QHBoxLayout;
    hbox->addWidget(line_edit);
    hbox->setContentsMargins(0, 0, 0, 0);
    hbox->setSpacing(0);
    setLayout(hbox);
    connect(line_edit, &PythonInterpreterLineEdit::returnPressed, this, &PythonInterpreterWidget::sendCommand);

    // setup python interpreter
    try
    {
        interpreter = py::module::import("interactive_console").attr("MyInteractiveInterpreter")();
        updateGlobals(interpreter.attr("getGlobals")().cast<py::dict>());
        line_edit->updateCompleter("");
    }
    catch (const std::exception& exc)
    {
        qWarning("%s", exc.what());
    }
}

void PythonInterpreterWidget::updatePrompt(const QString &prompt)
{
    line_edit->setPlaceholderText(prompt);
}

void PythonInterpreterWidget::updateGlobals(pybind11::dict globals)
{
    line_edit->_globals = globals;
}

void PythonInterpreterWidget::sendCommand()
{
    line_edit->_history.push_back(line_edit->text());
    line_edit->_history_index = line_edit->_history.size();
    try
    {
        qDebug() << line_edit->text();
        interpreter.attr("executeCommand")(line_edit->text().toStdString());
    }
    catch (const std::exception& exc)
    {
        qWarning("%s", exc.what());
    }
    line_edit->clear();
}
